package com.bendibao.online_poster.config;

import com.bendibao.online_poster.utils.ThreadLocalUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;

import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;


import java.util.Properties;

@Component
//拦截StatementHandler类中参数类型为Statement的prepare方法（prepare=在预编译SQL前加入修改的逻辑）
//即拦截 Statement prepare(Connection var1, Integer var2) 方法
@Intercepts({
		@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
		@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
@Slf4j
public class DataScopeInterceptor implements Interceptor {

	private static final String UNUSED_SQL = "SELECT * FROM (#{sql}) ev WHERE eigenvalue in (#{eigenvalue})";

	private static final String ORIGIN_SQL = "#{sql}";

	private static final String EIGENVALUE = "#{eigenvalue}";


	@Override
	public Object intercept(Invocation invocation) throws Throwable {

		String eigenvalue = ThreadLocalUtils.get();
		if(eigenvalue!=null){
			MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
			BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]);
			String sql = boundSql.getSql();
			String middleSql = UNUSED_SQL.replace(ORIGIN_SQL, sql);
			String nowSql = middleSql.replace(EIGENVALUE, eigenvalue);
			sqlHandle(mappedStatement, new MySqlSource(boundSql), invocation, nowSql);
		}
		return invocation.proceed();
	}


	private void sqlHandle(MappedStatement ms, SqlSource sqlSource, Invocation invocation, String newSql) {
		// 组装 MappedStatement
		MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), sqlSource, ms.getSqlCommandType());
		builder.resource(ms.getResource());
		builder.fetchSize(ms.getFetchSize());
		builder.statementType(ms.getStatementType());
		builder.keyGenerator(ms.getKeyGenerator());
		if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
			StringBuilder keyProperties = new StringBuilder();
			for (String keyProperty : ms.getKeyProperties()) {
				keyProperties.append(keyProperty).append(",");
			}
			keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
			builder.keyProperty(keyProperties.toString());
		}
		builder.timeout(ms.getTimeout());
		builder.parameterMap(ms.getParameterMap());
		builder.resultMaps(ms.getResultMaps());
		builder.resultSetType(ms.getResultSetType());
		builder.cache(ms.getCache());
		builder.flushCacheRequired(ms.isFlushCacheRequired());
		builder.useCache(ms.isUseCache());
		MappedStatement newMappedStatement = builder.build();
		MetaObject metaObject =  MetaObject.forObject(newMappedStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
		metaObject.setValue("sqlSource.boundSql.sql", newSql);
		invocation.getArgs()[0] = newMappedStatement;
	}

	// 定义一个内部类，作用是包装sql
	class MySqlSource implements SqlSource {

		private BoundSql boundSql;

		public MySqlSource(BoundSql boundSql) {
			this.boundSql = boundSql;
		}

		@Override
		public BoundSql getBoundSql(Object parameterObject) {
			return boundSql;
		}
	}


	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
	}

}